﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;

namespace CoffeePotServer
{
    /// <summary>
    /// File: Client_Handler.cs
    /// Author: James Dibble 10009689
    /// Last Revision: 17/03/12
    /// SVN Address:
    /// http://code.google.com/p/cnos-coffeepot-server/source/browse/branches/DibbleBranch/CoffeePotServerLib/CoffeePotServerLib/Client_Handler.cs
    /// </summary>
    public class Client_Handler
    {
        #region Attributes
        private Socket _sktClient;
        private NetworkStream _netStream;
        private Server_Response _serverResponse;
        private Client_Response _clientResponse;
        private String _strHostname;
        private String _strLastMessage = "";
        #endregion

        #region Constructor
        /// <summary>
        /// Create an object to deal with client interation.
        /// </summary>
        /// <param name="thisSocket"></param>
        public Client_Handler(Socket thisSocket)
        {
            _sktClient = thisSocket;
            _netStream = new NetworkStream(thisSocket, true);
            _serverResponse = new Server_Response(_netStream);
            _clientResponse = new Client_Response(_netStream, _sktClient);
            _strHostname = String.Empty;
        }
        #endregion

        #region Properties
        public String Hostname
        {
            get
            {
                if (_strHostname != String.Empty) return _strHostname;
                else
                {
                    try
                    {
                        _strHostname = Dns.GetHostEntry(((IPEndPoint)_sktClient.RemoteEndPoint).Address).HostName;
                        return _strHostname;
                    }
                    catch (Exception ex)
                    {
                        _strHostname = ((IPEndPoint)_sktClient.RemoteEndPoint).Address.ToString();
                        return _strHostname;
                    }
                }
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// It is best to run this method in a seperate thread if you are attempting a conncurrent server.
        /// This method reads responses from the client until they disconnect.
        /// </summary>
        public void HandleClient()
        {
            String strClientResponse = "";
            try
            {
                do
                {
                    //Get the next message from the client.
                    strClientResponse = _clientResponse.ReadLine();
                    if (HTCPCPServer.DebugMode && strClientResponse != "") Console.WriteLine("[{0}] {1}: {2}",
                                                                                            DateTime.Now.ToLongTimeString(),
                                                                                            this.Hostname,
                                                                                            strClientResponse.Replace("\\", "\\\\"));
                    ClientMessage thisMessage = new ClientMessage(strClientResponse);
                    try
                    {
                        thisMessage = thisMessage.ParseMessage();
                        //If a repeated message has occured, check whether the client has sent it as safe.
                        if (strClientResponse == _strLastMessage && thisMessage.IsSafe != ClientMessage.Safe.YES)
                        {
                            throw new ClientResponseException("Invalid Request", ClientResponseException.ExceptionType.INVALIDREQUEST);
                        }
                        else
                        {
                            _strLastMessage = strClientResponse;
                        }
                        Pot thisPot;
                        //Has the client requested a valid pot?
                        if ((thisPot = HTCPCPServer.FindPotByID(thisMessage.PotRequested)) == null)
                            _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(HTCPCPServer.StatusCodes.NOTFOUND));
                        else
                        {
                            //Is the pot not ready and not currently owned by this client?
                            if (thisPot.State != Pot.PotState.OK && thisPot.Owner != this)
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.BUSY));
                            //Is the pot ready?
                            else if (thisPot.State == Pot.PotState.OK)
                            {
                                thisPot.Owner = this;
                                HandleMessage(thisMessage, thisPot);
                            }
                            //Is this pot owned by this client?
                            else if (thisPot.Owner == this)
                            {
                                HandleMessage(thisMessage, thisPot);
                            }
                        }
                    }
                    catch (ClientResponseException ex)
                    {
                        if (HTCPCPServer.DebugMode && strClientResponse != "") Console.WriteLine("[{0}] {1}: {2}",
                                                                                        DateTime.Now.ToLongTimeString(),
                                                                                        this.Hostname,
                                                                                        ex.Message);
                        //Send the client a status code relating to the error thrown by the message parser.
                        switch (ex.Type)
                        {
                            case ClientResponseException.ExceptionType.INVALIDREQUEST:
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(HTCPCPServer.StatusCodes.BADREQUEST));
                                break;
                            case ClientResponseException.ExceptionType.INVALIDMETHOD:
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(HTCPCPServer.StatusCodes.INVALIDMETHOD));
                                break;
                            case ClientResponseException.ExceptionType.INVALIDPOT:
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(HTCPCPServer.StatusCodes.NOTFOUND));
                                break;
                            case ClientResponseException.ExceptionType.INVALIDADDITION:
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.NOT_ACEEPT));
                                break;
                            case ClientResponseException.ExceptionType.NOTPOURING:
                                _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.NOT_POURING));
                                break;
                        }
                    }
                } while (_sktClient.Connected && _netStream.CanRead && strClientResponse != "");
                Console.WriteLine("[{0}] {1} Diconnected",
                                  DateTime.Now.ToLongTimeString(),
                                  this.Hostname);
            }
            catch (IOException ex)
            {
                Console.WriteLine("[{0}] {1} Diconnected",
                                  DateTime.Now.ToLongTimeString(),
                                  this.Hostname);
            }
        }

        /// <summary>
        /// Deal with the client request.
        /// </summary>
        /// <param name="thisMessage">The message recieved from the client.</param>
        /// <param name="thisPot">The pot that was requested by the client.</param>
        private void HandleMessage(ClientMessage thisMessage, Pot thisPot)
        {
            switch (thisMessage.Method)
            {
                //Shouldn't ever get here but for good measure...
                case ClientMessage.Methods.NOTSET:
                    _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(HTCPCPServer.StatusCodes.INVALIDMETHOD));
                    break;
                //POST and BREW methods should be handled equivilently.
                case ClientMessage.Methods.BREW:
                case ClientMessage.Methods.POST:
                    if (thisMessage.Additions == null) thisPot.Brew();
                    else thisPot.Brew(thisMessage.Additions);
                    _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.POURING));
                    break;
                case ClientMessage.Methods.WHEN:
                    try
                    {
                        thisPot.When();
                        _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(thisPot.State));
                    }
                    catch (ClientResponseException ex)
                    {
                        if (ex.Type == ClientResponseException.ExceptionType.NOTPOURING)
                        {
                            _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.NOT_POURING));
                        }
                    }
                    break;
                case ClientMessage.Methods.GET:
                    _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(thisPot.State));
                    break;
                case ClientMessage.Methods.PROPFIND:
                    _serverResponse.SendLine(HTCPCPServer.GetEnumDescription(Pot.PotState.OK) + CreatePropfind());
                    break;
            }
        }

        private String CreatePropfind()
        {
            String strReturnVal = "\r\nAccept-additions: ";
            foreach (Addition.AdditionType addThis in Enum.GetValues(typeof(Addition.AdditionType)))
                strReturnVal += addThis.ToString() + ";";
            return strReturnVal.Substring(0, strReturnVal.Length - 1);
        }
        #endregion
    }
}